#pragma once

#include "type.h"
#include "Thread.h"

namespace Common
{
	class WorkQueue
	{
	public:

		typedef unsigned int RequestID;

		class Request
		{
			friend class WorkQueue;
		protected:
			/// The request channel, as an integer 
			uint16 m_uChannel;
			/// The request type, as an integer within the channel (user can define enumerations on this)
			uint16 m_uType;
			/// The details of the request (user defined)
			Any m_anyData;
			/// Retry count - set this to non-zero to have the request try again on failure
			uint16 m_uRetryCount;
			/// Identifier (assigned by the system)
			RequestID m_uID;
			/// Abort Flag
			mutable bool m_bAborted;

		public:
			/// Constructor 
			Request(uint16 channel, uint16 rtype, const Any& rData, uint16 retry, RequestID rid);
			~Request();
			/// Set the abort flag
			void abortRequest() const { m_bAborted = true; }
			/// Get the request channel (top level categorisation)
			uint16 getChannel() const { return m_uChannel; }
			/// Get the type of this request within the given channel
			uint16 getType() const { return m_uType; }
			/// Get the user details of this request
			const Any& getData() const { return m_anyData; }
			/// Get the remaining retry count
			uint16 getRetryCount() const { return m_uRetryCount; }
			/// Get the identifier of this request
			RequestID getID() const { return m_uID; }
			/// Get the abort flag
			bool getAborted() const { return m_bAborted; }
		};

		class Response
		{
			/// Pointer to the request that this response is in relation to
			const Request* m_pRequest;
			/// Whether the work item succeeded or not
			bool m_bSuccess;
			/// Any diagnostic messages
			std::string m_strMessages;
			/// Data associated with the result of the process
			Any m_anyData;

		public:
			Response(const Request* rq, bool success, const Any& data, const std::string& msg = "");
			~Response();
			/// Get the request that this is a response to (NB destruction destroys this)
			const Request* getRequest() const { return m_pRequest; }
			/// Return whether this is a successful response
			bool succeeded() const { return m_bSuccess; }
			/// Get any diagnostic messages about the process
			const std::string& getMessages() const { return m_strMessages; }
			/// Return the response data (user defined, only valid on success)
			const Any& getData() const { return m_anyData; }
			/// Abort the request
			void abortRequest() 
			{  
				if( m_pRequest == NULL )
					return;
				m_pRequest->abortRequest(); 
			}
		};

		class RequestHandler
		{
		public:
			RequestHandler(){}
			virtual ~RequestHandler(){}

			virtual bool canHandleRequest(const Request* req, const WorkQueue* srcQ){ (void)srcQ; return !req->getAborted(); }
			virtual Response* handleRequest(const Request* req, const WorkQueue* srcQ) = 0;
		};

		class ResponseHandler
		{
		public:
			ResponseHandler(){}
			virtual ~ResponseHandler(){}

			virtual bool canHandleResponse(const Response* res, const WorkQueue* srcQ){ (void)srcQ; return !res->getRequest()->getAborted(); }
			virtual void handleResponse(const Response* res, const WorkQueue* srcQ) = 0;
		};

		WorkQueue( const std::string& strWorkQueueName = "" );
		virtual ~WorkQueue(void);

		virtual void startup(bool forceRestart) = 0;
		virtual void shutdown() = 0;

		virtual void addRequestHandler(uint16 channel, RequestHandler* rh);
		virtual void removeRequestHandler(uint16 channel, RequestHandler* rh);
		virtual void addResponseHandler(uint16 channel, ResponseHandler* rh);
		virtual void removeResponseHandler(uint16 channel, ResponseHandler* rh);

		virtual RequestID addRequest(uint16 channel, uint16 requestType, const Any& rData, uint16 retryCount = 0, bool forceSynchronous = false);
		virtual void abortRequest(RequestID id);
		virtual void abortAllRequests();
		virtual void abortRequestsByChannel(uint16 channel);

		virtual void setPaused(bool bPause);
		virtual bool isPaused() const;
		virtual void setRequestsAccepted(bool accept);
		virtual bool getRequestsAccepted() const;
		virtual void processResponses();
		virtual bool isShuttingDown() const;

		unsigned long getResponseProcessingTimeLimit() const;
		void setResponseProcessingTimeLimit(unsigned long ms);

		const std::string& getName() const;
		uint16 getChannel(const std::string& strChannelName);

		virtual uint16 getWorkerThreadCount() const;

		/** Set the number of worker threads that this queue will start
		when startup() is called (default 1).
		Calling this will have no effect unless the queue is shut down and
		restarted.
		*/
		virtual void setWorkerThreadCount(size_t threadCount);

		/** Get whether worker threads will be allowed to access render system
		resources. 
		Accessing render system resources from a separate thread can require that
		a context is maintained for that thread. Also, it requires that the
		render system is running in threadsafe mode, which only happens
		when THREAD_SUPPORT=1. This option defaults to false, which means
		that threads can not use GPU resources, and the render system can 
		work in non-threadsafe mode, which is more efficient.
		*/
		virtual bool getWorkersCanAccessRenderSystem() const;


		/** Set whether worker threads will be allowed to access render system
		resources. 
		Accessing render system resources from a separate thread can require that
		a context is maintained for that thread. Also, it requires that the
		render system is running in threadsafe mode, which only happens
		when THREAD_SUPPORT=1. This option defaults to false, which means
		that threads can not use GPU resources, and the render system can 
		work in non-threadsafe mode, which is more efficient.
		Calling this will have no effect unless the queue is shut down and
		restarted.
		*/
		virtual void setWorkersCanAccessRenderSystem(bool access);

		virtual void _processNextRequest( void );
		virtual void _threadMain( void ) = 0;
		virtual void notifyWorkers( void ) = 0;

		virtual void addRequestWithRID(RequestID rid, uint16 channel, uint16 requesttype, const Any& rData, uint16 retryCount);

		virtual void processResponse(Response* r);
		virtual Response* processRequest(Request* r);

		//if synchronous is true, we will handle the response immediately after the request has been handled.
		//else we just queue the response into the response queue
		virtual void processRequestResponse(Request* r, bool synchronous);

	protected:

		std::string m_strWorkQueueName;
		bool m_bWorkerRenderSystemAccess;

		bool m_bIsRunning;
		bool m_bShuttingDown;
		bool m_bAcceptRequests;
		bool m_bPaused;

		unsigned long m_uResponseTimeLimitMS;

		typedef std::map<std::string, uint16> ChannelMap;
		ChannelMap m_ChannelMap;

		uint16 m_nextChannel;

		size_t m_uWorkThreadCount;

		/// Thread function
		struct WorkerFunc THREAD_WORKER_INHERIT
		{
			WorkQueue* m_pTargetWorkQueue;

			WorkerFunc(WorkQueue* pWorkQueue) 
				: m_pTargetWorkQueue(pWorkQueue) {}

			void operator()();

			void run();
		};
		WorkerFunc* m_pWorkerFunc;

		//for response and request storage
		MUTEX(m_mtxChannelMapMutex);
		MUTEX(m_mtxResponseMutex);
		MUTEX(m_mtxRequestMutex);
		MUTEX(m_mtxProcessMutex);
		RW_MUTEX(m_mtxRequestHandlerMutex);

		typedef std::deque<Request*> RequestQueue;
		typedef std::deque<Response*> ResponseQueue;

		RequestID	 m_uRequestCount;
		RequestQueue m_RequestQueue;
		RequestQueue m_ProcessQueue; 
		ResponseQueue m_ResponseQueue;

		//for response and request handlers, mapped by channels
		typedef std::list<RequestHandler*> RequestHandlerList;
		typedef std::list<ResponseHandler*> ResponseHandlerList;
		typedef std::map<uint16, RequestHandlerList> RequestHandlerListByChannel;
		typedef std::map<uint16, ResponseHandlerList> ResponseHandlerListByChannel;

		RequestHandlerListByChannel  m_RequestHandlers;
		ResponseHandlerListByChannel m_ResponseHandlers;
	};

}